package program_storage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import virtual_machine.Code;
import virtual_machine.Opcode;

public class Parser {

	/**
	 * Lexer that provide the stream of tokens.
	 */
	private final Lexer lexer;

	/**
	 * The actual token that is processed.
	 */
	private Token curr;

	/**
	 * The debug option
	 */
	private boolean debug = false;
	
	/**
	 * The Map that provide codelist for each method
	 */
	private Map<Integer, List<Code>> methodCodeMap= new HashMap<Integer, List<Code>>();
	
	/**
	 * The List that provide the code of the main method
	 */
	private List<Code> mainCodeList = new ArrayList<Code>();
	
	/**
	 * The Map that provide the number of arguments of each method
	 */
	private Map<Integer, Integer> methodToArgNumber = new HashMap<Integer, Integer>();
	
	/**
	 * Constructor for Parser
	 * @param methodToArg Map<Integer, Integer>
	 * @param lexer Lexer
	 * @param methodCodeMap Map<Integer, List<Code>>
	 * @param mainCodeList List<Code>
	 */
	public Parser(Map<Integer, Integer> methodToArg, Lexer lexer, Map<Integer, List<Code>> methodCodeMap, List<Code> mainCodeList) {
		this.lexer = lexer;
		this.methodCodeMap = methodCodeMap;
		this.mainCodeList = mainCodeList;
		this.methodToArgNumber = methodToArg;
	}

	/**
	 * Getter for {@link #methodToArgNumber}
	 * @return {@link #methodToArgNumber}
	 */
	public Map<Integer, Integer> getMethodArgNumber() {
		return this.methodToArgNumber;
	}
	
	/**
	 * Getter for {@link #methodCodeMap}
	 * @return {@link #methodCodeMap}
	 */
	public Map<Integer, List<Code>> getMethodMap() {
		return this.methodCodeMap;
	}
	
	/**
	 * Getter for {@link #mainCodeList}
	 * @return {@link #mainCodeList}
	 */
	public List<Code> getMainCodeList() {
		return this.mainCodeList;
	}
	
	/**
	 * Returns true if the current token has the given Type
	 *
	 * @param t expected token type
	 * @return true, if the current token has the given TokenType
	 */
	private boolean peek(TokenType t) {
		if (curr.is(t)) {
			return true;
		}
		else
			return false;
	}
	
	/**
	 * Returns the current Token, if its TokenType is the given TokenType
	 * @param tt TokenType
	 * @return the current token
	 * @throws ParserException, if the current Token has another Type
	 */
	private Token accept(TokenType tt) throws ParserException {
		if (debug) {
			System.err.println(tt.toString());
		}
		if (peek(tt)) {
			Token res = curr;
			curr = lexer.next();
			return res;
		}
		throw new ParserException(tt.name()+curr.toString()+curr.getLine()+"Unknown or unexpected virtual machine instruction");
	}
	
	/**
	 * Parses the complete Bytecode-File and fills up the {@link #methodCodeMap},
	 *  {@link #mainCodeList}, {@link #methodToArgNumber}
	 * @throws ParserException, if the Bytecode File has no correct Token sequence
	 */
	public void parse() throws ParserException {
		if (lexer.hasNext()) {
			this.curr = this.lexer.next();
		}
		else {
			throw new ParserException("ByteCode File was empty!");
		}
		while (!peek(TokenType.EOF)) {
			if (peek(TokenType.METHOD)) {
				parseMethod();
				accept(TokenType.SEMI);
			}
			else if (peek(TokenType.MAIN)) {
				parseMain();
				accept(TokenType.SEMI);
			}
			else {
				throw new ParserException("Unknown part in ByteCode File OR: now EndOfFile");
			}
		}
	}

	/**
	 * Parses a Method
	 * @throws ParserException, if the body of the method contains a wrong token sequence
	 */
	private void parseMethod() throws ParserException {
		accept(TokenType.METHOD);
		int methodNumber = parseInt();
		int argNumber = parseInt();
		accept(TokenType.COLON);
		this.methodCodeMap.put(methodNumber, parseCodeList());
		this.methodToArgNumber.put(methodNumber, argNumber);
	}
	
	/**
	 * Parses the Main-Method
	 * @throws ParserException, 
	 * 			if the body of the Main-Method contains a wrong token sequence
	 */
	private void parseMain() throws ParserException {
		accept(TokenType.MAIN);
		accept(TokenType.COLON);
		this.mainCodeList = parseCodeList();
	}
	
	/**
	 * Parses an Integer
	 * @return the integer
	 * @throws ParserException, if the Integer cannot be parsed
	 */
	private int parseInt() throws ParserException {
		try {
			return Integer.parseInt(accept(TokenType.INT_LITERAL).getText());
		}
		catch (NumberFormatException e) {
			throw new ParserException("ByteCodeFile contains too large Integer");
		}
	}

	/**
	 * Parses a Double
	 * @return the double
	 * @throws ParserException, if the Double cannot be parsed
	 */
	private double parseDouble() throws ParserException {
		try {
			return Double.parseDouble(accept(TokenType.DOUBLE_LITERAL).getText());
		}
		catch (NumberFormatException e) {
			throw new ParserException("ByteCodeFile contains too large Integer");
		}
	}
	
	/**
	 * Parses the body of a method
	 * @return a code list that the body represent
	 * @throws ParserException, if the body cannot be parsed
	 */
	private List<Code> parseCodeList() throws ParserException {
		List<Code> codeList = new ArrayList<Code>();
		while(!peek(TokenType.SEMI)) {
			if (peek(TokenType.ADD)) {
				accept(TokenType.ADD);
				codeList.add(new Code(Opcode.ADD));
			}
			else if (peek(TokenType.AND)){
				accept(TokenType.AND);
				codeList.add(new Code(Opcode.AND));
			}
			else if (peek(TokenType.LENGTH)){
				accept(TokenType.LENGTH);
				codeList.add(new Code(Opcode.LENGTH));
			}
			else if (peek(TokenType.DIV)){
				accept(TokenType.DIV);
				codeList.add(new Code(Opcode.DIV));
			}
			else if (peek(TokenType.GDIV)) {
				accept(TokenType.GDIV);
				codeList.add(new Code(Opcode.GDIV));
			}
			else if (peek(TokenType.MUL)){
				accept(TokenType.MUL);
				codeList.add(new Code(Opcode.MUL));
			}
			else if (peek(TokenType.MOD)){
				accept(TokenType.MOD);
				codeList.add(new Code(Opcode.MOD));
			}
			else if (peek(TokenType.SUB)) {
				accept(TokenType.SUB);
				codeList.add(new Code(Opcode.SUB));
			}
			else if (peek(TokenType.OR)){
				accept(TokenType.OR);
				codeList.add(new Code(Opcode.OR));
			}
			else if (peek(TokenType.DUP)){
				accept(TokenType.DUP);
				codeList.add(new Code(Opcode.DUP));
			}
			else if (peek(TokenType.XOR)){
				accept(TokenType.XOR);
				codeList.add(new Code(Opcode.XOR));
			}
			else if (peek(TokenType.EQUAL)){
				accept(TokenType.EQUAL);
				codeList.add(new Code(Opcode.EQUAL));
			}
			else if (peek(TokenType.NOTEQUAL)){
				accept(TokenType.NOTEQUAL);
				codeList.add(new Code(Opcode.NOTEQUAL));
			}
			else if (peek(TokenType.CALLMETH)){
				accept(TokenType.CALLMETH);
				codeList.add(new Code(Opcode.CALLMETH));
			}
			else if (peek(TokenType.SHIFTL)){
				accept(TokenType.SHIFTL);
				codeList.add(new Code(Opcode.SHIFTL));
			}
			else if (peek(TokenType.SHIFTR)){
				accept(TokenType.SHIFTR);
				codeList.add(new Code(Opcode.SHIFTR));
			}
			else if (peek(TokenType.INTTOBOOL)){
				accept(TokenType.INTTOBOOL);
				codeList.add(new Code(Opcode.INTTOBOOL));
			}
			else if (peek(TokenType.LESS)){
				accept(TokenType.LESS);
				codeList.add(new Code(Opcode.LESS));
			}
			else if (peek(TokenType.LESSEQUAL)){
				accept(TokenType.LESSEQUAL);
				codeList.add(new Code(Opcode.LESSEQUAL));
			}
			else if (peek(TokenType.GREATER)){
				accept(TokenType.GREATER);
				codeList.add(new Code(Opcode.GREATER));
			}
			else if (peek(TokenType.GREATEREQUAL)){
				accept(TokenType.GREATEREQUAL);
				codeList.add(new Code(Opcode.GREATEREQUAL));
			}
			else if (peek(TokenType.LDIC)){
				accept(TokenType.LDIC);
				int op = parseInt();
				codeList.add(new Code(Opcode.LDIC, op));
			}
			else if (peek(TokenType.LDDC)){
				accept(TokenType.LDDC);
				double op = parseDouble();
				codeList.add(new Code(Opcode.LDDC, op));
			}
			else if (peek(TokenType.LOAD)){
				accept(TokenType.LOAD);
				int op = parseInt();
				codeList.add(new Code(Opcode.LOAD, op));
			}
			else if (peek(TokenType.STORE)){
				accept(TokenType.STORE);
				int op = parseInt();
				codeList.add(new Code(Opcode.STORE, op));
			}
			else if (peek(TokenType.WRITEINT)){
				accept(TokenType.WRITEINT);
				codeList.add(new Code(Opcode.WRITEINT));
			}
			else if (peek(TokenType.WRITEBOOL)){
				accept(TokenType.WRITEBOOL);
				codeList.add(new Code(Opcode.WRITEBOOL));
			}
			else if (peek(TokenType.WRITECHAR)){
				accept(TokenType.WRITECHAR);
				codeList.add(new Code(Opcode.WRITECHAR));
			}
			else if (peek(TokenType.WRITEDOUBLE)){
				accept(TokenType.WRITEDOUBLE);
				codeList.add(new Code(Opcode.WRITEDOUBLE));
			}
			else if (peek(TokenType.READINT)){
				accept(TokenType.READINT);
				codeList.add(new Code(Opcode.READINT));
			}
			else if (peek(TokenType.READCHAR)){
				accept(TokenType.READCHAR);
				codeList.add(new Code(Opcode.READCHAR));
			}
			else if (peek(TokenType.READDOUBLE)){
				accept(TokenType.READDOUBLE);
				codeList.add(new Code(Opcode.READDOUBLE));
			}
			else if (peek(TokenType.SEL)){
				accept(TokenType.SEL);
				codeList.add(new Code(Opcode.SEL));
			}
			else if (peek(TokenType.JUMP)){
				accept(TokenType.JUMP);
				int op = parseInt();
				codeList.add(new Code(Opcode.JUMP, op));
			}
			else if (peek(TokenType.JZ)){
				accept(TokenType.JZ);
				int op = parseInt();
				codeList.add(new Code(Opcode.JZ, op));
			}
			else if (peek(TokenType.GETVALUE)){
				accept(TokenType.GETVALUE);
				codeList.add(new Code(Opcode.GETVALUE));
			}
			else if (peek(TokenType.SETVALUE)){
				accept(TokenType.SETVALUE);
				codeList.add(new Code(Opcode.SETVALUE));
			}
			else if (peek(TokenType.NEW)){
				accept(TokenType.NEW);
				int op = parseInt();
				codeList.add(new Code(Opcode.NEW, op));
			}
			else if (peek(TokenType.HALT)){
				accept(TokenType.HALT);
				codeList.add(new Code(Opcode.HALT));
			}
			else if (peek(TokenType.RETURN)){
				accept(TokenType.RETURN);
				codeList.add(new Code(Opcode.RETURN));
			}
			else if (peek(TokenType.RETURNVALUE)){
				accept(TokenType.RETURNVALUE);
				codeList.add(new Code(Opcode.RETURNVALUE));
			}
			else {
				throw new ParserException(curr.toString()+curr.getLine()+"Undefined Token or Token Sequence");
			}
		}
		return codeList;
	}
	
}


